#include "combination_io.h"
#include "logger.h"
#include "samples_preprocessor.h"

CCombinationIO::CCombinationIO(ISampleIO *pReadIO, ISampleIO *pWriteIO)
		:mReadIO(pReadIO),
		mWriteIO(pWriteIO),
		mRecoding(false),
		mReadCodec(NULL),
		mWriteCodec(NULL),
		mDoubleSampleBuf(NULL),
		mDoubleSampleCount(0),
		mByteBuf(NULL),
		mByteCount(0)
{
}

CCombinationIO::~CCombinationIO()
{
	if(mReadIO) {
		delete mReadIO;
	}
	if(mWriteIO) {
		delete mWriteIO;
	}
	if(mReadCodec) {
		delete mReadCodec;
	}
	if(mWriteCodec) {
		delete mWriteCodec;
	}
	getDoubleBuffer(0); // free the intern buffer for double samples
	getByteBuffer(0); // free the intern byte buffer
}

int CCombinationIO::init(CPropertiesReader *pConfig)
{
	bool lInitError(false);
	if(mReadIO) {
		if(mReadIO->init(pConfig) != 0) {
			lInitError = true;
		}
	}
	if(mWriteIO) {
		if(mWriteIO->init(pConfig) != 0) {
			lInitError = true;
		}
	}
	if(lInitError) {
		return -1;
	}
	if(mReadIO && mWriteIO) {
		if(mReadIO->getStreamInfo().mSampleRate
		!= mWriteIO->getStreamInfo().mSampleRate) {
			printl(LL_ERR, __FILE__, "Doesn't support different sample rates."
					" read: %u, write: %u\n",
					mReadIO->getStreamInfo().mSampleRate,
					mWriteIO->getStreamInfo().mSampleRate);
			return -1;
		}
		if((mReadIO->getStreamInfo().mSamplesWidth
		!= mWriteIO->getStreamInfo().mSamplesWidth)
		|| (mReadIO->getStreamInfo().mCodec
		!= mWriteIO->getStreamInfo().mCodec)) {
			mReadCodec = CSamplesPreprocessor::newCodecPerCodecInfo(
					mReadIO->getStreamInfo().mCodec);
			mWriteCodec = CSamplesPreprocessor::newCodecPerCodecInfo(
					mWriteIO->getStreamInfo().mCodec);
			if(!mReadCodec || !mWriteCodec) {
				printl(LL_ERR, __FILE__, "Codec failed for recoding.\n");
				return -1;
			}
			printl(0, __FILE__, "Recoding is used.\n");
			mRecoding = true;
		}
	}
	return 0;
}

int CCombinationIO::destroy()
{
	bool lDestroyError(false);
	if(mReadIO) {
		if(mReadIO->destroy() != 0) {
			lDestroyError = true;
		}
	}
	if(mWriteIO) {
		if(mWriteIO->destroy() != 0) {
			lDestroyError = true;
		}
	}
	if(lDestroyError) {
		return -1;
	}
	return 0;
}

int CCombinationIO::read(void** pData, EAction& pAction, int pTimeout)
{
	if(!mReadIO) {
		return 0;
	}
	return mReadIO->read(pData, pAction, pTimeout);
}

int CCombinationIO::write(void* pData, const EAction& pAction, int pLength)
{
	if(!mWriteIO || pLength < 0) {
		return 0;
	}
	if(mRecoding) {
		NAudioFormats::SStreamInfo lReadStreamInfo(mReadIO->getStreamInfo());
		unsigned int lReadBytePerSample =
				CSamplesPreprocessor::getBytePerSample(
						lReadStreamInfo.mSamplesWidth);
		unsigned int lSampleLength(pLength/lReadBytePerSample);
		double* lDoubleSamples = getDoubleBuffer(lSampleLength);

		if(CSamplesPreprocessor::convertFromIntToDouble(pData,
				lDoubleSamples,
				lSampleLength,
				lReadStreamInfo,
				mReadCodec) != lSampleLength) {
			printl(LL_ERR, __FILE__, "Convert from integer to double samples failed.\n");
			return -1;
		}

		NAudioFormats::SStreamInfo lWriteStreamInfo(mWriteIO->getStreamInfo());
		unsigned int lWriteBytePerSample =
				CSamplesPreprocessor::getBytePerSample(
						lWriteStreamInfo.mSamplesWidth);
		int lWriteBufLen = lSampleLength * lWriteBytePerSample;
		void* lWriteBuf = getByteBuffer(lWriteBufLen);

		if(CSamplesPreprocessor::convertFromDoubleToInt(lDoubleSamples,
				lWriteBuf,
				lSampleLength,
				lWriteStreamInfo,
				mWriteCodec) != lSampleLength) {
			printl(LL_ERR, __FILE__, "Convert from double to integer samples failed.\n");
			return -1;
		}

		printl(0, __FILE__, "Recoding samples.\n");
		int lWriteCount = mWriteIO->write(lWriteBuf, pAction, lWriteBufLen);
		return (lWriteCount / lWriteBytePerSample) * lReadBytePerSample;
	}

	return mWriteIO->write(pData, pAction, pLength);
}

NAudioFormats::SStreamInfo CCombinationIO::getStreamInfo()
{
	// if read io exist then use the infos from read io otherwise from write io
	if(mReadIO) {
		return mReadIO->getStreamInfo();
	}
	if(mWriteIO) {
		return mWriteIO->getStreamInfo();
	}
	printl(LL_WARN, __FILE__, "Currently no stream infos available.\n");
	return NAudioFormats::SStreamInfo(NAudioFormats::UNKNOWN_WIDTH,
			0,
			NAudioFormats::UNKNOWN_CODEC);
}

double* CCombinationIO::getDoubleBuffer(int pSampleLength)
{
	if((!mDoubleSampleBuf && mDoubleSampleCount > 0)
	|| (mDoubleSampleBuf && mDoubleSampleCount <= 0)) {
		printl(LL_ERR, __FILE__, "Inconsistent buffer for double samples\n");
		return NULL;
	}
	if(pSampleLength <= 0) {
		if(mDoubleSampleBuf) {
			delete [] mDoubleSampleBuf;
			mDoubleSampleBuf = NULL;
			mDoubleSampleCount = 0;
		}
		return NULL;
	}

	// check if current buffer is too small
	if(pSampleLength > mDoubleSampleCount) {
		delete [] mDoubleSampleBuf;
		mDoubleSampleBuf = new double [pSampleLength];
		mDoubleSampleCount = pSampleLength;
	}
	return mDoubleSampleBuf;
}

void* CCombinationIO::getByteBuffer(int pByteLength)
{
	if((!mByteBuf && mByteCount > 0)
	|| (mByteBuf && mByteCount <= 0)) {
		printl(LL_ERR, __FILE__, "Inconsistent byte buffer\n");
		return NULL;
	}
	if(pByteLength <= 0) {
		if(mByteBuf) {
			delete [] mByteBuf;
			mByteBuf = NULL;
			mByteCount = 0;
		}
		return NULL;
	}

	// check if current buffer is too small
	if(pByteLength > mByteCount) {
		delete [] mByteBuf;
		mByteBuf = new char [pByteLength];
		mByteCount = pByteLength;
	}
	return mByteBuf;
}
